/*******************************************************************************
Copyright (C) 2018 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is 
provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*******************************************************************************/

/*jslint plusplus: true, nomen: true, indent: 4, white: true, evil: true*/
/*global qsTr, BinaryStream*/

BinaryStream.NULL = 0;
BinaryStream.UNDEFINED = 1;
BinaryStream.FUNCTION = 2;
BinaryStream.STRING = 3;
BinaryStream.CSTRING = 4;
BinaryStream.NUMBER = 5;
BinaryStream.BOOL = 6;
BinaryStream.ARRAY = 7;
BinaryStream.OBJECT = 8;
BinaryStream.CUSTOMOBJECT = 9;
BinaryStream.BOOLEAN = 10;

BinaryStream.prototype.writeObj = function (aObj) {
	"use strict";
	var type = typeof aObj, ks, i;
	
	if (aObj === undefined) {
		this.writeInt8(BinaryStream.UNDEFINED);
		return true;
	}

	if (aObj === null) {
		this.writeInt8(BinaryStream.NULL);
		return true;
	}

	if (type === "function") {
		this.writeInt8(BinaryStream.FUNCTION);
		return true;
	}
	
	if (type === "string") {
		if (aObj.isLatin1()) {
			this.writeInt8(BinaryStream.CSTRING);
			this.writeCString(aObj);
			return true;
		}
		
		this.writeInt8(BinaryStream.STRING);
		this.writeString(aObj);
		return true;
	}
	
	if (type === "number") {
		this.writeInt8(BinaryStream.NUMBER);
		this.writeReal64(aObj);
		return true; 
	}

	if (type === "boolean") {
		this.writeInt8(BinaryStream.BOOLEAN);
		this.writeBool(aObj);
		return true;
	}
	
	if (type === "object") {
		if (aObj.__store__ !== undefined) {
			throw "Recursive storing detected!";
		}
		
		Object.defineProperty(aObj, "__store__", {
			value: true,
			writable: true,
			enumerable: false
		});
		
		if (Array.isArray(aObj)) {
			this.writeInt8(BinaryStream.ARRAY);
			
			this.writeInt32(aObj.length);
			for (i = 0; i < aObj.length; ++i) {
				this.writeObj(aObj[i]);
			}
		} else if (typeof(aObj.storeObj) === "function" &&
				   typeof(aObj.constructorName) === "string") {
			this.writeInt8(BinaryStream.CUSTOMOBJECT);
			this.writeString(aObj.constructorName);
			aObj.storeObj(this);
		} else {
			this.writeInt8(BinaryStream.OBJECT);
			ks = Object.keys(aObj);
			this.writeInt32(ks.length);
			ks.forEach(function (key) {
				this.writeString(key);
				this.writeObj(aObj[key]);
			}, this);
		}
		aObj.__store__ = undefined;
		return true;
	}

	throw qsTr("Unknown type");
};

BinaryStream.prototype.readObj = function () {
	'use strict';
	
	var type = this.readInt8(), value, len, key, constructorName, i;

	if (type === BinaryStream.UNDEFINED || type === BinaryStream.FUNCTION) {
		return undefined;
	}

	if (type === BinaryStream.NULL) {
		return null;
	}

	if (type === BinaryStream.CSTRING) {
		value = this.readCString();
		return value;
	}
	
	if (type === BinaryStream.STRING) {
		value = this.readString();
		return value;
	}

	if (type === BinaryStream.NUMBER) {
		value = this.readReal64();
		return value;
	}

	if (type === BinaryStream.BOOLEAN) {
		value = this.readBool();
		return value;
	}

	if (type === BinaryStream.ARRAY) {
		len = this.readInt32();
		value = [];
		for (i = 0; i < len; ++i) {
			value[i] = this.readObj();
		}
		return value;
	}

	if (type === BinaryStream.CUSTOMOBJECT) {
		constructorName = this.readString();
		value = eval("new {0}()".format(constructorName));
		value.loadObj(this);
		return value;
	}

	if (type === BinaryStream.OBJECT) {
		len = this.readInt32();
		value = {};

		while (len) {
			key = this.readString();
			value[key] = this.readObj();
			--len;
		}
		return value;
	}

	throw qsTr("Unknown type %1".arg(type));
};
